package com.liangwj.springbootredis.RedisConfig;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import java.time.Duration;

/**
 * <b>Description:</b> redis的缓存配置 <br/>
 * @author Lingwenjun
 * @date 2018/12/14-16:26
 * @version 1.0
 */
@Configuration
@EnableCaching//此注解很重要 开启缓存
public class RedisCacheConfig extends CachingConfigurerSupport {

    @Bean
    public KeyGenerator keyGenerator() {
        return (o, method, objects) -> { //lambda  public Object generate(Object o, Method method, Object... obejcts)
            StringBuilder sb = new StringBuilder();
            sb.append(o.getClass().getName());
            sb.append(method.getName());
            for (Object object : objects) {
                sb.append(object.toString());
            }
            return sb.toString();
        };
    }

    /*@Deprecated springboot 1.5的 写法 ,2.0已经change了
    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
        //设置缓存过期时间
        ((RedisCacheManager) rcm).setDefaultExpiration(60);//单位秒
        return rcm;
    }*/

    /**
     * springboot 2.0 的用法
     * @return
     * @version 2.0
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {

        //可以直接返回默认的RedisCacheManager 或者 返回自定义configuration后的RedisCacheManager
        //1. 默认返回默认的RedisCacheManager
        //return RedisCacheManager.create(redisConnectionFactory);

        //2.返回 builder自定义的redisCacheConfiguration
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(Duration.ofHours(1)) //设置缓存过期时间 key experation time
                    .disableCachingNullValues(); //禁用缓存空值

        //RedisCacheManager.cacheDefaults(自定义的redisCacheConfiguration)
        return RedisCacheManager.builder(
                        RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory) //默认为无锁
                        //无锁缓存可提高吞吐量。缺少条目锁定可能导致putIfAbsent和clean方法的重叠非原子命令，因为那些需要将多个命令发送到Redis。
                        // 锁定对应方通过设置显式锁定密钥并检查是否存在此密钥来防止命令重叠，这会导致其他请求和潜在的命令等待时间。
                        //RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory) //
                    )
                    .cacheDefaults(redisCacheConfiguration)
                    .build();

        /*官方示例:RedisCacheManager rcm = RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(defaultCacheConfig())
                .withInitialCacheConfigurations(singletonMap("predefined", defaultCacheConfig().disableCachingNullValues()))
                .transactionAware()
                .build();
        return rcm;*/
    }

    /**
     * 为什么是 RedisTemplate<String, String> 而不是  RedisTemplate<String, Object> ,value的String是用JSON 序列化的
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        // redis 自带的
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}
